/*
 * Copyright 2007-2019 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.ymate.platform.persistence.jdbc;

import com.mx.jdbc.ymate.config.DataSourceConfig;
import com.mx.jdbc.ymate.config.JdbcProperties;
import com.mx.jdbc.ymate.contants.JdbcContants;
import net.ymate.platform.commons.util.ClassUtils;
import net.ymate.platform.commons.util.RuntimeUtils;
import net.ymate.platform.core.persistence.IDataSourceRouter;
import net.ymate.platform.persistence.jdbc.annotation.DataSourceAdapter;
import net.ymate.platform.persistence.jdbc.annotation.Dialect;
import net.ymate.platform.persistence.jdbc.dialect.IDialect;
import net.ymate.platform.persistence.jdbc.impl.DefaultDatabaseConnectionHolder;
import net.ymate.platform.persistence.jdbc.impl.DefaultDatabaseSession;
import net.ymate.platform.persistence.jdbc.transaction.ITransaction;
import net.ymate.platform.persistence.jdbc.transaction.Transactions;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author 刘镇 (suninformation@163.com) on 2011-9-10 下午11:45:25
 */
@Component
public final class JDBC implements IDatabase {


    @Autowired
    private JdbcProperties jdbcProperties;

    private static final Log LOG = LogFactory.getLog(JDBC.class);

    /**
     * 框架提供的已知数据源适配器名称映射
     */
    public static final Map<String, String> DS_ADAPTERS;

    /**
     * 框架提供的已知数据库连接驱动
     */
    public static final Map<String, String> DB_DRIVERS;

    /**
     * 提供的已知数据库方言
     */
    public static final Map<String, Class<? extends IDialect>> DB_DIALECTS;

    private static Map<String, IDatabaseDataSourceAdapter> dataSourceCaches = new ConcurrentHashMap<>();

    private static final Map<String, DataSourceConfig> DATA_SOURCE_CONFIG_MAP = new ConcurrentHashMap<>();

    private static boolean initialized;

    static {
        Map<String, Class<? extends IDialect>> dbDialects = new HashMap<>();
        Map<String, String> dbDrivers = new HashMap<>();
        Map<String, String> dbAdapters = new HashMap<>();
        try {
            ClassUtils.getExtensionLoader(IDialect.class, true).getExtensionClasses().forEach(dialectClass -> {
                Dialect dialectAnn = dialectClass.getAnnotation(Dialect.class);
                if (dialectAnn != null) {
                    dbDialects.put(dialectAnn.value(), dialectClass);
                    if (StringUtils.isNotBlank(dialectAnn.driverClass())) {
                        dbDrivers.put(dialectAnn.value(), dialectAnn.driverClass());
                    }
                }
            });
            ClassUtils.getExtensionLoader(IDatabaseDataSourceAdapter.class, true).getExtensionClasses().forEach(adapterClass -> {
                DataSourceAdapter adapterAnn = adapterClass.getAnnotation(DataSourceAdapter.class);
                if (adapterAnn != null) {
                    dbAdapters.put(adapterAnn.value(), adapterClass.getName());
                }
            });
        } catch (Exception e) {
            if (LOG.isWarnEnabled()) {
                LOG.warn(StringUtils.EMPTY, RuntimeUtils.unwrapThrow(e));
            }
        }
        DB_DIALECTS = Collections.unmodifiableMap(dbDialects);
        DB_DRIVERS = Collections.unmodifiableMap(dbDrivers);
        DS_ADAPTERS = Collections.unmodifiableMap(dbAdapters);
    }

    @PostConstruct
    public void initialize() throws Exception {
        if (!initialized) {
            String[] dsNameList = StringUtils.split(jdbcProperties.getDsNamelist(), "|");
            jdbcProperties.getDatasources().forEach(dataSourceConfig -> DATA_SOURCE_CONFIG_MAP.put(dataSourceConfig.getName(), dataSourceConfig));
            for (String dsName : dsNameList) {
                if (DS_ADAPTERS.containsKey(dsName)) {
                    IDatabaseDataSourceAdapter dataSourceAdapter = ClassUtils.impl(DS_ADAPTERS.get(dsName), IDatabaseDataSourceAdapter.class, JDBC.class);
                    dataSourceAdapter.initialize(DATA_SOURCE_CONFIG_MAP.get(dsName));
                    dataSourceCaches.put(dsName, dataSourceAdapter);
                }
            }
            initialized = true;
        }
    }

    public JDBC() {
    }

    @PreDestroy
    public void close() throws Exception {
        if (initialized) {
            initialized = false;
            for (IDatabaseDataSourceAdapter adapter : dataSourceCaches.values()) {
                adapter.close();
            }
            dataSourceCaches = null;
        }
    }

    @Override
    public IDatabaseConnectionHolder getDefaultConnectionHolder() throws Exception {
        return getConnectionHolder(jdbcProperties.getDsDefaultName());
    }

    private IDatabaseDataSourceAdapter doSafeGetDataSourceAdapter(String dataSourceName) {
        IDatabaseDataSourceAdapter dataSourceAdapter = dataSourceCaches.get(dataSourceName);
        if (dataSourceAdapter == null) {
            throw new IllegalStateException(String.format("Datasource '%s' not found.", dataSourceName));
        }
        return dataSourceAdapter;
    }

    @Override
    public IDatabaseConnectionHolder getConnectionHolder(String dataSourceName) throws Exception {
        IDatabaseConnectionHolder connectionHolder;
        ITransaction transaction = Transactions.get();
        if (transaction != null) {
            connectionHolder = transaction.getConnectionHolder(dataSourceName);
            if (connectionHolder == null) {
                connectionHolder = new DefaultDatabaseConnectionHolder(doSafeGetDataSourceAdapter(dataSourceName));
                transaction.registerConnectionHolder(connectionHolder);
            }
        } else {
            connectionHolder = new DefaultDatabaseConnectionHolder(doSafeGetDataSourceAdapter(dataSourceName));
        }
        return connectionHolder;
    }

    @Override
    public void releaseConnectionHolder(IDatabaseConnectionHolder connectionHolder) throws Exception {
        // 需要判断当前连接是否参与事务，若存在事务则不进行关闭操作
        if (Transactions.get() == null) {
            if (connectionHolder != null) {
                connectionHolder.close();
            }
        }
    }

    @Override
    public DataSourceConfig getDefaultDataSourceConfig() {
        return DATA_SOURCE_CONFIG_MAP.get(JdbcContants.DEFAULT_DS);
    }

    @Override
    public DataSourceConfig getDataSourceConfig(String dataSourceName) {
        return DATA_SOURCE_CONFIG_MAP.get(dataSourceName);
    }

    @Override
    public IDatabaseDataSourceAdapter getDefaultDataSourceAdapter() {
        return getDataSourceAdapter(jdbcProperties.getDsDefaultName());
    }

    @Override
    public IDatabaseDataSourceAdapter getDataSourceAdapter(String dataSourceName) {
        return doSafeGetDataSourceAdapter(dataSourceName);
    }

    @Override
    public <T> T openSession(IDatabaseSessionExecutor<T> executor) throws Exception {
        return openSession(getDefaultConnectionHolder(), executor);
    }

    @Override
    public <T> T openSession(String dataSourceName, IDatabaseSessionExecutor<T> executor) throws Exception {
        return openSession(getConnectionHolder(dataSourceName), executor);
    }

    @Override
    public <T> T openSession(IDatabaseConnectionHolder connectionHolder, IDatabaseSessionExecutor<T> executor) throws
            Exception {
        try (IDatabaseSession session = new DefaultDatabaseSession(this, connectionHolder)) {
            return executor.execute(session);
        }
    }

    @Override
    public <T> T openSession(IDataSourceRouter dataSourceRouter, IDatabaseSessionExecutor<T> executor) throws
            Exception {
        return openSession(getConnectionHolder(dataSourceRouter.getDataSourceName()), executor);
    }

    @Override
    public IDatabaseSession openSession() throws Exception {
        return new DefaultDatabaseSession(this, getDefaultConnectionHolder());
    }

    @Override
    public IDatabaseSession openSession(String dataSourceName) throws Exception {
        return new DefaultDatabaseSession(this, getConnectionHolder(dataSourceName));
    }

    @Override
    public IDatabaseSession openSession(IDatabaseConnectionHolder connectionHolder) throws Exception {
        return new DefaultDatabaseSession(this, connectionHolder);
    }

    @Override
    public IDatabaseSession openSession(IDataSourceRouter dataSourceRouter) throws Exception {
        return new DefaultDatabaseSession(this, getConnectionHolder(dataSourceRouter.getDataSourceName()));
    }
}
